/*
 *  FILE: open.c
 *  AUTH: mcc | jal
 *  DESC:
 *  DATE: Mon Apr  6 19:27:49 1998
 */

#include "globals.h"
#include "errno.h"
#include "fs/fcntl.h"
#include "util/string.h"
#include "util/printf.h"
#include "fs/vfs.h"
#include "fs/vnode.h"
#include "fs/file.h"
#include "fs/vfs_syscall.h"
#include "fs/open.h"
#include "fs/stat.h"
#include "util/debug.h"

/* find empty index in p->p_files[] */
int
get_empty_fd(proc_t *p)
{
        int fd;

        for (fd = 0; fd < NFILES; fd++) {
                if (!p->p_files[fd])
                        return fd;
        }

        dbg(DBG_ERROR | DBG_VFS, "ERROR: get_empty_fd: out of file descriptors "
            "for pid %d\n", curproc->p_pid);
        return -EMFILE;
}

/*
 * There a number of steps to opening a file:
 *      1. Get the next empty file descriptor.
 *      2. Call fget to get a fresh file_t.
 *      3. Save the file_t in curproc's file descriptor table.
 *      4. Set file_t->f_mode to OR of FMODE_(READ|WRITE|APPEND) based on
 *         oflags, which can be O_RDONLY, O_WRONLY or O_RDWR, possibly OR'd with
 *         O_APPEND.
 *      5. Use open_namev() to get the vnode for the file_t.
 *      6. Fill in the fields of the file_t.
 *      7. Return new fd.
 *
 * If anything goes wrong at any point (specifically if the call to open_namev
 * fails), be sure to remove the fd from curproc, fput the file_t and return an
 * error.
 *
 * Error cases you must handle for this function at the VFS level:
 *      o EINVAL   done
 *        oflags is not valid.
 *      o EMFILE   done
 *        The process already has the maximum number of files open.
 *      o ENOMEM   done
 *        Insufficient kernel memory was available.
 *      o ENAMETOOLONG    done
 *        A component of filename was too long.
 *      o ENOENT  done
 *        O_CREAT is not set and the named file does not exist.  Or, a
 *        directory component in pathname does not exist.
 *      o EISDIR done 
 *        pathname refers to a directory and the access requested involved
 *        writing (that is, O_WRONLY or O_RDWR is set).
 *      o ENXIO
 *        pathname refers to a device special file and no corresponding device
 *        exists.
 */

int
do_open(const char *filename, int oflags)
{
    dbgq(DBG_THR, "do_open(), enter do_open(), curproc is %s\n", curproc->p_comm);
/* Yu code below v0.9 */
    char *path_piece_ptr;
    size_t pathname_len;
    int path_piece_len;
    
    int new_fd;
    vnode_t *file_vnode;
    file_t *file_table_entry;
    
    int retval;
    
    /* verify the pathname length */
    if (filename == NULL) {
	return -EINVAL;
    }
    pathname_len = strlen(filename);
    if( pathname_len > MAXPATHLEN) {
        return -ENAMETOOLONG;
    }
    if (pathname_len < 1) {
	return -EINVAL;
    }
    
    /* verify oflags */
    if ( oflags & ~(O_RDONLY|O_WRONLY|O_RDWR|O_CREAT|O_TRUNC|O_APPEND) ) {
        return -EINVAL;
    }
    if ((oflags & O_WRONLY) && (oflags & O_RDWR)) {
	return -EINVAL;
    } 

    /* verify the pathname format */
    /* unlike the standard UNIX, all characters in pathname must be printable here */
    for( path_piece_ptr = (char*) filename, path_piece_len=0 ;
           *path_piece_ptr != '\0'; 
           path_piece_ptr++  ){
    	    if( *path_piece_ptr != '/' ) {
    	        path_piece_len ++;
    	        if (path_piece_len > NAME_LEN ) {
    	            return -ENAMETOOLONG;
    	        }
    	    } else {
    	    	 path_piece_len = 0;
    	    }
    	}

    /* get the next empty file descriptor */
    new_fd = get_empty_fd(curproc);
    if ( new_fd == -EMFILE ) {
        return new_fd;
    }
    
    /* get a fresh file_t */
    file_table_entry = fget( -1);
    if( file_table_entry == NULL ) {
        return -ENOMEM;
    }
    
    /* Save the file_t in curproc's file descriptor table */
    curproc->p_files[new_fd] = file_table_entry;
    
    /* Set file_t->f_mode */
    if( oflags & O_RDWR ) {
        file_table_entry->f_mode = FMODE_READ|FMODE_WRITE;
    } else {
        if ( oflags & O_WRONLY ) {
            file_table_entry->f_mode = FMODE_WRITE;
        } else {
            file_table_entry->f_mode = FMODE_READ;
        }
    }
    if ( oflags & O_APPEND ) {
          file_table_entry->f_mode |= FMODE_APPEND;
    }
    
    /* get the vnode for the file_t */
    dbgq(DBG_THR, "do_open():  call open_namev\n, curproc is %s\n", curproc->p_comm);
    retval = open_namev(filename, oflags, &file_vnode, NULL); 
    dbgq(DBG_THR, "do_open(), open_namev return\n, curproc is %s\n", curproc->p_comm);
    if ( retval != 0 ) {
        /* clean up before return */
    	dbgq(DBG_THR, "do_open(), open_namev error\n, curproc is %s\n", curproc->p_comm);
        fput( file_table_entry );
        curproc->p_files[new_fd] = NULL;
        return retval; /* -ENAMETOOLONG or -ENOTDIR or -ENOENT or -ENOSPC */
    }
    
    /* Fill in the fields of the file_t */
    file_table_entry->f_vnode = file_vnode;
    
    /* check whether try to write a directory */
    if ( S_ISDIR( file_vnode->vn_mode ) && ( file_table_entry->f_mode & FMODE_WRITE ) ) {
         /* clean up before return */
    	 dbgq(DBG_THR, "do_open(), directory error\n, curproc is %s\n", curproc->p_comm);
         fput( file_table_entry );
         curproc->p_files[new_fd] = NULL;
         return -EISDIR;
    }
    
    /* if contain O_TRUNC flag, modify the file len */
    /* NEED_TO_DO: */
     
    dbgq(DBG_THR, "do_open(), leave do_open()\n, curproc is %s\n", curproc->p_comm);
    return new_fd;
/* Yu code above v0.9 */
}
